<template>
  <view
    class="v-input-number"
    :class="{ 'with-action': action }"
    :style="customStyle"
  >
    <view
      v-if="action"
      class="action"
      :class="{ disable: reduceDisabled }"
      @click="reduceHandle"
    >
      -
    </view>
    <input
      v-model="inputValue"
      :type="type"
      :placeholder="placeholder"
      :placeholder-style="placeholderStyle"
      :placeholder-class="placeholderClass"
      :disabled="disabled"
      :focus="focus"
      :cursor-spacing="cursorSpacing"
      class="input"
      @input="inputHandle"
      @focus="(event: FocusEvent) => $emit('focus', event)"
      @blur="blurHandle"
      @confirm="() => $emit('confirm')"
    >
    <view
      v-if="action"
      class="action"
      :class="{ disable: addDisabled }"
      @click="addHandle"
    >
      +
    </view>
  </view>
</template>

<script setup lang="ts">
import type { CSSProperties } from 'vue';

/** https://uniapp.dcloud.net.cn/component/input.html#input */
const props = withDefaults(
  defineProps<{
    modelValue?: number;
    customStyle?: CSSProperties;
    type?: 'number' | 'digit';
    placeholder?: string;
    placeholderStyle?: string;
    placeholderClass?: string;
    disabled?: boolean;
    precision?: number;
    max?: number;
    min?: number;
    focus?: boolean;
    confirmType?: string;
    cursorSpacing?: number;
    action?: boolean;
  }>(),
  {
    modelValue: undefined,
    replace: undefined,
    customStyle: undefined,
    type: undefined,
    placeholder: '',
    placeholderStyle: undefined,
    placeholderClass: 'placeholder',
    disabled: false,
    precision: 2,
    max: undefined,
    min: undefined,
    focus: false,
    confirmType: undefined,
    cursorSpacing: 12,
    action: false
  }
);

interface Emits {
  (event: 'update:model-value', val: number | undefined): void;
  (event: 'input', val: number | undefined): void;
  (event: 'focus', val: FocusEvent): void;
  (event: 'blur', val: FocusEvent): void;
  (event: 'confirm'): void;
  (event: 'paste', val: Event): void;
}
const emits = defineEmits<Emits>();

const inputValue = ref('');

const modelValue = toRef(props, 'modelValue');
const type = toRef(props, 'type');
const precision = toRef(props, 'precision');
const max = toRef(props, 'max');
const min = toRef(props, 'min');

watch(
  modelValue,
  (val) => {
    const text = val?.toString?.() ?? '';
    if (text !== inputValue.value) {
      inputValue.value = text;
    }
  },
  { immediate: true }
);

const reduceDisabled = computed(() => {
  if (props.modelValue === undefined) return true;
  return props.min !== undefined && props.modelValue <= props.min;
});
const addDisabled = computed(() => {
  if (props.modelValue === undefined) return true;
  return props.max !== undefined && props.modelValue >= props.max;
});

const reduceHandle = () => {
  if (reduceDisabled.value) return;
  let number = props.modelValue ?? 0;
  number -= 1;
  if (typeof min.value === 'number' && number < min.value) {
    number = min.value;
  }
  inputValue.value = number.toFixed(precision.value);
  emits('update:model-value', number);
};
const addHandle = () => {
  if (addDisabled.value) return;
  let number = props.modelValue ?? 0;
  number += 1;
  if (typeof max.value === 'number' && number > max.value) {
    number = max.value;
  }
  inputValue.value = number.toFixed(precision.value);
  emits('update:model-value', number);
};

const inputHandle = async (event: Event) => {
  const value = (event as InputEvent).detail.value;
  inputValue.value = value;
  let formated = value;
  if (type.value === 'number' || !precision.value) {
    formated = formated.replace(/[^\d]/g, '');
  } else if (type.value === 'digit') {
    formated = formated.replace(/[^\d.]/g, '');
    const index = formated.indexOf('.');
    if (index !== -1) {
      const pre = formated.slice(0, index);
      const suf = formated.slice(index + 1);
      formated = `${pre}.${suf.slice(0, precision.value)}`;
    }
  }
  await new Promise((resolve) => nextTick(() => resolve(true)));
  const number = formated ? parseFloat(formated) : undefined;
  inputValue.value = formated;
  await new Promise((resolve) => nextTick(() => resolve(true)));
  emits('input', number);
};

const blurHandle = async (event: FocusEvent) => {
  if (inputValue.value) {
    let number = parseFloat(inputValue.value);
    if (typeof number === 'number') {
      if (typeof max.value === 'number' && number > max.value) {
        number = max.value;
      }
      if (typeof min.value === 'number' && number < min.value) {
        number = min.value;
      }
    }
    inputValue.value = number.toFixed(precision.value);
    emits('update:model-value', number);
  } else {
    emits('update:model-value', undefined);
  }
  emits('blur', event);
};
</script>

<style lang="scss" scoped>
.v-input-number {
  font-size: 28rpx;
  padding: 12rpx 0;
  margin: -12rpx 0;
  background-color: transparent;
  width: 100%;
  .input {
    font-size: inherit;
  }
  &.with-action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    .input {
      width: 80rpx;
      text-align: center;
    }
  }
  .action {
    height: 60rpx;
    width: 60rpx;
    border-radius: 8rpx;
    background-color: #f5f6fa;
    font-size: 36rpx;
    line-height: 60rpx;
    text-align: center;
    font-weight: bold;
    &.disable {
      opacity: 0.5;
      color: #cccccc;
    }
  }
}
</style>
